package org.elsfs.cloud.module.datasource.biz.manager;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.tools.generic.DateTool;
import org.apache.velocity.tools.generic.MathTool;
import org.elsfs.cloud.common.core.utils.SpringContextHolder;
import org.elsfs.cloud.common.util.lang.BeanUtils;
import org.elsfs.cloud.common.util.lang.NamingCase;
import org.elsfs.cloud.common.util.lang.StringUtils;
import org.elsfs.cloud.module.datasource.biz.entity.GenGroup;
import org.elsfs.cloud.module.datasource.biz.entity.GenTemplate;
import org.elsfs.cloud.module.datasource.biz.repository.GenGroupRepository;
import org.elsfs.cloud.module.datasource.biz.repository.GenTemplateRepository;
import org.elsfs.cloud.module.datasource.biz.repository.mapper.GenTemplateGroupMapper;
import org.elsfs.cloud.module.datasource.biz.repository.mapper.GeneratorMapper;
import org.elsfs.cloud.module.datasource.biz.vo.GenTableVo;
import org.elsfs.cloud.module.datasource.biz.vo.TableColumnVO;
import org.elsfs.cloud.module.dict.api.entity.GenDatasourceConf;
import org.elsfs.cloud.module.dict.api.enums.DsJdbcUrlEnum;
import org.elsfs.cloud.module.dict.api.repository.mapper.GenDatasourceConfMapper;
import org.elsfs.cloud.screw.engine.EngineFileType;
import org.elsfs.cloud.screw.spring.Screw;
import org.elsfs.cloud.screw.spring.ScrewProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成
 *
 * @author zeng
 */
@Component
@RequiredArgsConstructor
public class GeneratorManager {
  private final Screw screw;
  private final ScrewProperties screwProperties;
  private final GenGroupRepository genGroupRepository;
  private final GenTemplateRepository genTemplateRepository;
  private final GenTemplateGroupMapper genTemplateGroupMapper;

  /**
   * 获取数据源对应方言的mapper
   *
   * @param dsName 数据源名称
   * @return GeneratorMapper
   */
  public static GeneratorMapper getMapper(String dsName) {
    // 获取目标数据源数据库类型
    GenDatasourceConfMapper datasourceConfMapper =
      SpringContextHolder.getBean(GenDatasourceConfMapper.class);
    GenDatasourceConf datasourceConf =
      datasourceConfMapper.selectOne(
        Wrappers.<GenDatasourceConf>lambdaQuery().eq(GenDatasourceConf::getName, dsName));
    // 默认MYSQL 数据源
    String dbConfType =
      datasourceConf == null ? DsJdbcUrlEnum.MYSQL.getDbName() : datasourceConf.getDsType();

    // 获取全部数据实现
    ApplicationContext context = SpringContextHolder.getApplicationContext();
    Map<String, GeneratorMapper> beansOfType = context.getBeansOfType(GeneratorMapper.class);
    // 根据数据类型选择mapper
    for (String key : beansOfType.keySet()) {
      if (StringUtils.containsIgnoreCase(key, dbConfType)) {
        return beansOfType.get(key);
      }
    }

    throw new IllegalArgumentException("dsName 不合法: " + dsName);
  }

  /**
   * 查询数据源对应的文档
   *
   * @param dsName 数据源名称
   */
  @SneakyThrows
  public ByteArrayOutputStream generatorDoc(String dsName, EngineFileType engineFileType) {
    // 设置指定的数据源
    DynamicRoutingDataSource dynamicRoutingDataSource =
      SpringContextHolder.getBean(DynamicRoutingDataSource.class);
    DynamicDataSourceContextHolder.push(dsName);
    DataSource dataSource = dynamicRoutingDataSource.determineDataSource();
    ScrewProperties properties = new ScrewProperties();
    BeanUtils.copyProperties(screwProperties, properties);
    properties.setFileType(engineFileType);
    // 生成
    return screw.documentGeneration(dsName, dataSource, properties);
  }

  /**
   * 查询数据源对应的文档
   *
   * @param dsName 数据源名称
   */
  @SneakyThrows
  public ByteArrayOutputStream generatorDoc(String dsName) {
    // 设置指定的数据源
    DynamicRoutingDataSource dynamicRoutingDataSource =
      SpringContextHolder.getBean(DynamicRoutingDataSource.class);
    DynamicDataSourceContextHolder.push(dsName);
    DataSource dataSource = dynamicRoutingDataSource.determineDataSource();
    // 生成
    return screw.documentGeneration(dsName, dataSource, screwProperties);
  }

  @SneakyThrows
  public void downloadCode(String tableId, ZipOutputStream zip) {

  }

  /**
   * 目标目录写入渲染结果
   *
   * @param tableId 表
   */
  public void generatorCode(String tableId) {

  }

  /**
   * 预览代码
   *
   * @param genTableVo 表
   * @return [{模板名称:渲染结果}] ⚠️对应的 map 不支持插入数据
   */
  public List<Map<String, String>> preview(GenTableVo genTableVo)  {
    // 获取模板列表
    List<GenTemplate> templateList = genTemplateRepository.getByGenGroupId(genTableVo.getGroupId());
    Map<String, Object> dataModel = BeanUtils.toMap(genTableVo);
    dataModel.put("packagePath", genTableVo.getPackageName().replace(".", File.separator));
    genTableVo.getFieldList().stream().filter(TableColumnVO::getIsPrimaryKey).findFirst()
        .ifPresent(primaryKey -> dataModel.put("primaryKey", primaryKey.getFieldName()));
    List<Map<String, String>> codes = new ArrayList<>();
    for (GenTemplate template : templateList) {
      String templateCode = template.getTemplateCode();
      String generatorPath = template.getGeneratorPath();
      String content = renderStr(templateCode, dataModel);
      String path = renderStr(generatorPath, dataModel);
      codes.add(
        Map.of("code", content, "codePath", path)
      );
    }
    return codes;
  }

  /**
   * 渲染文本
   *
   * @param str       数据
   * @param dataModel 数据
   * @return 渲染后的文本
   */
  private String renderStr(String str, Map<String, Object> dataModel) {
    // 设置velocity资源加载器
    Velocity.init();
    VelocityContext context = new VelocityContext(dataModel);
    // 函数库
    context.put("math", new MathTool());
    context.put("dateTool", new DateTool());
    context.put("dict", new DictTool());
    context.put("str", new NamingCaseTool());
    StringWriter stringWriter = new StringWriter();
    Velocity.evaluate(context, stringWriter, "renderStr", str);
    return stringWriter.toString();
  }


  /**
   * 通过 Lambda 表达式优化的获取数据模型方法
   *
   * @param dsName 表格 ID
   * @return 数据模型 Map 对象
   */
  private Map<String, Object> getDataModel(String dsName, String tableName) {
    GeneratorMapper mapper = getMapper(dsName);
    GenTableVo table = mapper.queryTable(tableName, dsName);
    Map<String, Object> map = new HashMap<>();
    map.put("table", table);
    List<TableColumnVO> columnVOS = mapper.queryTableColumnVo(tableName);
    map.put("column", columnVOS);
    return map;
  }

  /**
   * 字典工具
   *
   * @author zeng
   */
  public static class DictTool {
    public DictTool() {}

    /**
     * 将字段列表转换为带有双引号的逗号分隔的字符串
     *
     * @return 带有双引号的逗号分隔的字符串
     */
    public static String quotation(List<String> fields) {
      StringBuilder s = new StringBuilder();
      for (String field : fields) {
        if (s.length() > 0) {
          s.append(String.format(",'%s'", field));
        } else {
          s.append(String.format("'%s'", field));
        }
      }
      return s.toString();
    }

    /**
     * 将字段列表转换为逗号分隔的字符串
     *
     * @return 逗号分隔的字符串
     */
    public static String format(List<String> fields) {
      // 将字段列表转换为逗号分隔的字符串
      StringBuilder s = new StringBuilder();
      for (String field : fields) {
        s.append(String.format("%s,", field));
      }
      return s.toString();
    }
  }

  /** 命名规则处理，针对驼峰，下划线等处理 */
  public static class NamingCaseTool {

    /**
     * 传入字段获取的get方法
     *
     * @param in 字段名称
     * @return 方法名称
     */
    public static String getProperty(String in) {
      return String.format("get%s", NamingCase.toPascalCase(in));
    }

    public static String setProperty(String in) {
      return String.format("set%s", NamingCase.toPascalCase(in));
    }

    /**
     * 首字母大写
     *
     * @param in 字段
     * @return 首字母大写
     */
    public static String pascalCase(String in) {
      return String.format(NamingCase.toPascalCase(in));
    }
  }
}
